<?php
class UserSession{
	const UserName = "UserName";
	const UserId = "UserId";
	const UserDisplayName = "UserDisplayName";
    const IpAddress = "IpAddress";
    const UserAgent = "UserAgent";
    const SessionExpires = "SessionExpires";
    const FingerPrint = "FingerPrint";
    const FailedLogins = "FailedLogins";

	function __construct(){
		if(!session_id()){
            session_name("19h52obds8ha4h5vo15ke33n87"/*substr(sha1(mt_rand()), 5, 5)*/);
            session_start();
		}

        $this->ValidateSession();
	}

    //region properties

    public function UserLoggedIn(){
        return ($this->Property(self::FingerPrint) == $this->GetFingerPrint());
    }

    public function UserName($value = null){
        return $this->Property(self::UserName, $value);
    }

    public function UserId($value = null){
        return $this->Property(self::UserId, $value);
    }

    public function UserDisplayName($value = null){
        return $this->Property(self::UserDisplayName, $value);
    }

    public function PostTokenKey(){
        return $this->GeneratePostToken(true);
    }

    public function PostTokenValue(){
        return $this->GeneratePostToken(false);
    }

    //endregion properties

    //region SessionHelpers

    public function Property($key, $value = null){
        if($value == null){
            $value = $this->ValueGet($key);
        }
        else{
            $this->ValueSet($key, $value);
        }
        return $value;
    }

	private function ValueGet($key){
		if($this->ValueExists($key)){
			return $_SESSION[$key];
		} else {
			return null;
		}
	}

    private function ValueSet($key, $value){
		$_SESSION[$key] = $value;
		return true;
	}

    private function ValueRemove($key){
		if($this->ValueExists($key)){
			unset($_SESSION[$key]);
			return true;
		} else {
			return false;
		}
	}

    private function ValueExists($key){
		return isset($_SESSION[$key]);
	}

    //endregion SessionHelpers

	public function LoginFailed(){
        $count = $this->ValueExists(self::FailedLogins) ? $this->ValueGet(self::FailedLogins) : 0;
        $count++;
        $this->ValueSet(self::FailedLogins, $count);
	}

	public function Destroy(){
        session_destroy();
        session_start();
        session_regenerate_id(true);
        $_SESSION = array();
        return true;
	}

	public function Regenerate(){
        $this->Destroy();

        session_regenerate_id();

        ini_set('session.gc_maxlifetime', $this->GetTimeOut());

        $this->KeepAlive();

        $this->Property(self::FailedLogins, 0);
        $this->Property(self::FingerPrint, $this->GetFingerPrint());
        $this->Property(self::IpAddress, $this->ServerRemoteAddress());
        $this->Property(self::UserAgent, $this->ServerUserAgent());
	}

    public function KeepAlive(){
        $this->Property(self::SessionExpires, $this->GetExpireTime());
    }

    private function GetTimeOut() {
        return SESSION_LENGTH;
    }

    private function GetExpireTime() {
        return (time() + $this->GetTimeOut());
    }

    private function GetFingerPrint(){
        return md5($this->GenerateSalt(session_id()) . "TYuiajkKKG¤%889");
    }

    function ValidateSession() {
        try{
            if($this->ValueExists(self::FailedLogins)){
                if($this->ValueGet(self::FailedLogins) >= 5){
                    throw new Exception('5 times bad login attempts!');
                }
            }
            if($this->UserLoggedIn()){
                if($this->ValueExists(self::SessionExpires)){
                    if($this->ValueGet(self::SessionExpires) < time()){
                        throw new Exception('Attempt to use expired session!');
                    }
                }
                if($this->ValueExists(self::IpAddress)){
                    if($this->ValueGet(self::IpAddress) != $this->ServerRemoteAddress()){
                        throw new Exception('IP Address mixmatch (possible session hijacking attempt)!');
                    }
                }
                if($this->ValueExists(self::UserAgent)){
                    if($this->ValueGet(self::UserAgent) != $this->ServerUserAgent()){
                        throw new Exception('Useragent mixmatch (possible session hijacking attempt)!');
                    }
                }
            }
        }
        catch(Exception $e){
            $this->Destroy();
            echo $e->getMessage();
            Exit;
        }
    }

    //region tokens

    public function GeneratePostToken($isKeyToken){
        $salt = $this->GenerateSalt("JKhuioi(/&9");
        return $this->GenerateHash($isKeyToken ? "PostTokenKey" : "PostTokenValue", $salt, $salt);
    }

    private function GenerateSalt($pepper){
        return sha1($this->ServerUserAgent() . $pepper . "update+kr4y5rmxmkrx" . $this->ServerRemoteAddress());
    }

    public function ValidatePostToken($value, $isKeyToken){
        return ($this->GeneratePostToken($isKeyToken) == $value);
    }

    public function GenerateHash($plainText, $salt, $pepper){
        for($i = 0; $i <= SALT_LENGTH; $i++){
            $plainText = sha1(md5(md5($plainText.$i.$salt.($i+1)), sha1($plainText.($i+2).$pepper.($i+3))));
        }
        return $plainText;
    }

    //endregion tokens

    //region Server variables

    private function ServerRemoteAddress(){
        return $_SERVER['REMOTE_ADDR'];
    }

    private function ServerUserAgent(){
        return $_SERVER['HTTP_USER_AGENT'];
    }

    //endregion Server variables
}